﻿using Kang.DbDoc.Interfaces;
using Kang.DbDoc.Models;
using Kang.ExtendMethod;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace Kang.DbDoc.ServicesImpl
{
    public class ChmExportService : IExport
    {
        public bool Export(string filename, DatabaseModel database, List<TableOrViewModel> tables, List<TableOrViewModel> views, List<RoutineModel> functions, List<RoutineModel> procedures, ref string error)
        {
            try
            {
                var TplsDirPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Tpls\chm");
                var OutputDirPath = Path.Combine(App.AppDataDir, "Temp");
                var ProjectOutputDirPath = Path.Combine(OutputDirPath, database.Name);
                var ContentOutputDirPath = Path.Combine(ProjectOutputDirPath, "Contents");
                var TableOutputDirPath = Path.Combine(ContentOutputDirPath, "表");
                var ViewOutputDirPath = Path.Combine(ContentOutputDirPath, "视图");
                var FunctionOutputDirPath = Path.Combine(ContentOutputDirPath, "方法");
                var ProcedureOutputDirPath = Path.Combine(ContentOutputDirPath, "存储过程");
                var HHKFilePath = ProjectOutputDirPath.CombinePath("chm.hhk");
                var HHCFilePath = ProjectOutputDirPath.CombinePath("chm.hhc");
                var HHPFilePath = ProjectOutputDirPath.CombinePath("chm.hhp");
                var TocFilePath = ProjectOutputDirPath.CombinePath("toc.html");
                 
                CheckDirCreated(TableOutputDirPath);
                CheckDirCreated(ViewOutputDirPath);
                CheckDirCreated(FunctionOutputDirPath);
                CheckDirCreated(ProcedureOutputDirPath);

                var files = Directory.GetFiles(TplsDirPath, "*.js", SearchOption.AllDirectories);
                foreach (var filePath in files)
                {
                    var fileName = Path.GetFileName(filePath);
                    File.Copy(filePath, Path.Combine(ProjectOutputDirPath, fileName), true);
                }

                files = Directory.GetFiles(TplsDirPath, "*.css", SearchOption.AllDirectories);
                foreach (var filePath in files)
                {
                    var fileName = Path.GetFileName(filePath);
                    File.Copy(filePath, Path.Combine(ProjectOutputDirPath, fileName), true);
                }

                var HHKTpl = File.ReadAllText(Path.Combine(TplsDirPath, "hhk.tpl"));
                var hhkResult = HHKTpl
                    .Replace("<Index />" , GetHHKResultStringArray(tables, views, functions, procedures));
                hhkResult?.Replace("</LI>", "").WriteToFile(HHKFilePath, Encoding.GetEncoding("GBK"));
                
                var HHCTpl = File.ReadAllText(Path.Combine(TplsDirPath, "hhc.tpl"));
                var hhcResult = HHCTpl
                    .ReplaceAll(new[] { "<Tables />", "<Views />", "<Functions />", "<Procedures />" }, GetHHCResultStringArray(tables, views, functions, procedures));
                hhcResult?.Replace("</LI>", "").WriteToFile(HHCFilePath, Encoding.GetEncoding("GBK"));
                
                var TocTpl = File.ReadAllText(Path.Combine(TplsDirPath, "list.tpl"));
                var tocResult = TocTpl
                    .ReplaceAll(new[] { "<Tables />", "<Views />", "<Functions />", "<Procedures />" }, GetTocResultStringArray(tables, views, functions, procedures));
                tocResult.WriteToFile(TocFilePath, Encoding.GetEncoding("GBK"));

                var FilepathList = new List<string>();
                var TableAndViewTpl = File.ReadAllText(Path.Combine(TplsDirPath, "table_view.tpl"));
                foreach (var item in tables)
                {
                    var path = Path.Combine(TableOutputDirPath, $"{item.Name}.html");
                    var content = TableAndViewTpl
                        .ReplaceAll(new[] { "<TableName />", "<Comment />", "<Columns />" }, new [] { item.Name, item.Comment, GetColumnsResultString(item.Columns) });
                    content.WriteToFile(path, Encoding.GetEncoding("GBK"));
                    FilepathList.Add(path);
                }
                foreach (var item in views)
                {
                    var path = Path.Combine(ViewOutputDirPath, $"{item.Name}.html");
                    var content = TableAndViewTpl
                        .ReplaceAll(new[] { "<TableName />", "<Comment />", "<Columns />" }, new[] { item.Name, item.Comment, GetColumnsResultString(item.Columns) });
                    content.WriteToFile(path, Encoding.GetEncoding("GBK"));
                    FilepathList.Add(path);
                }
                var FunctionTpl = File.ReadAllText(Path.Combine(TplsDirPath, "function.tpl"));
                foreach (var item in functions)
                {
                    var path = Path.Combine(FunctionOutputDirPath, $"{item.Name}.html");
                    var content = FunctionTpl
                        .ReplaceAll(new[] { "<Name />", "<Comment />", "<Definer />", "<Definition />" }, new[] { item.Name, item.Comment.IsNotBlank() ? item.Comment : "&nbsp;", item.Definer, item.Definition });
                    content.WriteToFile(path, Encoding.GetEncoding("GBK"));
                    FilepathList.Add(path);
                }
                var ProcedureTpl = File.ReadAllText(Path.Combine(TplsDirPath, "procedure.tpl"));
                foreach (var item in procedures)
                {
                    var path = Path.Combine(ProcedureOutputDirPath, $"{item.Name}.html");
                    var content = ProcedureTpl
                        .ReplaceAll(new[] { "<Name />", "<Comment />", "<Definer />", "<Definition />" }, new[] { item.Name, item.Comment.IsNotBlank() ? item.Comment : "&nbsp;", item.Definer, item.Definition });
                    content.WriteToFile(path, Encoding.GetEncoding("GBK"));
                    FilepathList.Add(path);
                }

                var HHPTpl = File.ReadAllText(Path.Combine(TplsDirPath, "hhp.tpl"));
                var hhpResult = HHPTpl
                    .ReplaceAll(new[] { "<ChmFile />", "<DefaultFile />", "<Title />", "<Files />" }, GetHHPResultStringArray(filename, database.DatabaseName, FilepathList));
                hhpResult?.Replace("</LI>", "").WriteToFile(HHPFilePath, Encoding.GetEncoding("GBK"));

                string res = StartRun(HHPFilePath, Encoding.GetEncoding("GBK"));
                return true;
            }
            catch (Exception e)
            {
                error = e.Message;
                return false;
            }
        }

        private string StartRun(string arguments, Encoding encoding)
        {
            string str = "";
            ProcessStartInfo startInfo = new ProcessStartInfo()
            {
                FileName = "hhc.exe",
                Arguments = arguments,
                WindowStyle = ProcessWindowStyle.Hidden,
                RedirectStandardOutput = true,
                UseShellExecute = false,
                RedirectStandardError = true,
                CreateNoWindow = true,
                StandardErrorEncoding = encoding,
                StandardOutputEncoding = encoding
            };

            using (Process process = Process.Start(startInfo))
            {
                using (StreamReader reader = process.StandardOutput)
                {
                    str = reader.ReadToEnd();
                }
                process.WaitForExit();
            }
            return str.Trim();
        }

        private void CheckDirCreated(string dirpath)
        {
            if (dirpath.AsDirectoryPathAndExists() != true)
            {
                Directory.CreateDirectory(dirpath);
            }
        }

        /*
         * 目录
         */
        private string[] GetHHCResultStringArray(List<TableOrViewModel> tables, List<TableOrViewModel> views, List<RoutineModel> functions, List<RoutineModel> procedures)
        {
            var result = new string[4];
            var list = new List<string>();
            tables.IsNotBlank(x =>
            {
                list.Add(@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""" + x.Name + @""">
                <param name=""Local"" value=""Contents\表\" + x.Name + @".html"">
                </OBJECT>");
            });
            tables.IsNotBlank(() =>
            {
                result[0] = @"<LI> <OBJECT type=""text/sitemap"">
        <param name=""Name"" value=""表"">
        </OBJECT>
        <UL>
" + list.Join("") + $"{Environment.NewLine}        </UL>";
            });

            list.Clear();
            views.IsNotBlank(x =>
            {
                list.Add(@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""" + x.Name + @""">
                <param name=""Local"" value=""Contents\视图\" + x.Name + @".html"">
                </OBJECT>");
            });
            views.IsNotBlank(() =>
            {
                result[1] = @"<LI> <OBJECT type=""text/sitemap"">
        <param name=""Name"" value=""视图"">
        </OBJECT>
        <UL>
" + list.Join("") + $"{Environment.NewLine}        </UL>";
            });

            list.Clear();
            functions.IsNotBlank(x =>
            {
                list.Add(@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""" + x.Name + @""">
                <param name=""Local"" value=""Contents\方法\" + x.Name + @".html"">
                </OBJECT>");
            });
            functions.IsNotBlank(() =>
            {
                result[2] = @"<LI> <OBJECT type=""text/sitemap"">
        <param name=""Name"" value=""方法"">
        </OBJECT>
        <UL>
" + list.Join("") + $"{Environment.NewLine}        </UL>";
            });

            list.Clear();
            procedures.IsNotBlank(x =>
            {
                list.Add(@"
            <LI> <OBJECT type=""text/sitemap"">
                <param name=""Name"" value=""" + x.Name + @""">
                <param name=""Local"" value=""Contents\存储过程\" + x.Name + @".html"">
                </OBJECT>");
            });
            procedures.IsNotBlank(() =>
            {
                result[3] = @"<LI> <OBJECT type=""text/sitemap"">
        <param name=""Name"" value=""存储过程"">
        </OBJECT>
        <UL>
" + list.Join("") + $"{Environment.NewLine}        </UL>";
            });
            return result;
        }

        /*
         * 索引
         */
        private string GetHHKResultStringArray(List<TableOrViewModel> tables, List<TableOrViewModel> views, List<RoutineModel> functions, List<RoutineModel> procedures)
        {
            var list = new List<string>();
            tables.IsNotBlank(x =>
            {
                list.Add(@"        <LI> <OBJECT type=""text/sitemap"">
            <param name=""Name"" value=""" + x.Name + @""">
            <param name=""Local"" value=""Contents\表\" + x.Name + @".html"">
            </OBJECT>");
            });
            
            views.IsNotBlank(x =>
            {
                list.Add(@"        <LI> <OBJECT type=""text/sitemap"">
            <param name=""Name"" value=""" + x.Name + @""">
            <param name=""Local"" value=""Contents\视图\" + x.Name + @".html"">
            </OBJECT>");
            });

            functions.IsNotBlank(x =>
            {
                list.Add(@"        <LI> <OBJECT type=""text/sitemap"">
            <param name=""Name"" value=""" + x.Name + @""">
            <param name=""Local"" value=""Contents\方法\" + x.Name + @".html"">
            </OBJECT>");
            });

            procedures.IsNotBlank(x =>
            {
                list.Add(@"        <LI> <OBJECT type=""text/sitemap"">
            <param name=""Name"" value=""" + x.Name + @""">
            <param name=""Local"" value=""Contents\存储过程\" + x.Name + @".html"">
            </OBJECT>");
            });
            return list.Join(Environment.NewLine);
        }

        /*
         * toc.html
         */
        private string[] GetTocResultStringArray(List<TableOrViewModel> tables, List<TableOrViewModel> views, List<RoutineModel> functions, List<RoutineModel> procedures)
        {
            var result = new string[4];
            var list = new List<string>();
            var index = 0;
            tables.IsNotBlank(x =>
            {
                list.Add(@"
                <tr>
                    <td>"+ ++index + @"</td>
                    <td colspan=""3"">
                        <a href=""Contents/表/"+ x.Name + @".html"">" + x.Name + @"</a>
                    </td>
                    <td colspan=""2"">"+ (x.Comment.IsNotBlank() ? x.Comment : "&nbsp;") + @"</td>
                </tr>");
            });
            tables.IsNotBlank(() =>
            {
                result[0] = @"
        <table bordercolorlight=""#D7D7E5"" bordercolordark=""#D3D8E0"">
            <caption>
                <h2>表</h2>
            </caption>
            <tr bgcolor=""#F0F0F0"" style='font-weight: bold;'>
                <td>序号</td>
                <td colspan=""3"">表名称</td>
                <td colspan=""2"">表说明</td>
            </tr>
" + list.Join("") + "        </table>";
            });

            list.Clear();
            index = 0;
            views.IsNotBlank(x =>
            {
                list.Add(@"
                <tr>
                    <td>"+ ++index + @"</td>
                    <td colspan=""3"">
                        <a href=""Contents/视图/" + x.Name + @".html"">" + x.Name + @"</a>
                    </td>
                    <td colspan=""2"">" + (x.Comment.IsNotBlank() ? x.Comment : "&nbsp;") + @"</td>
                </tr>");
            });
            views.IsNotBlank(() =>
            {
                result[1] = @"
        <table bordercolorlight=""#D7D7E5"" bordercolordark=""#D3D8E0"">
            <caption>
                <h2>视图</h2>
            </caption>
            <tr bgcolor=""#F0F0F0"" style='font-weight: bold;'>
                <td>序号</td>
                <td colspan=""3"">视图名称</td>
                <td colspan=""2"">视图说明</td>
            </tr>
" + list.Join("") + "        </table>";
            });

            list.Clear();
            index = 0;
            functions.IsNotBlank(x =>
            {
                list.Add(@"
                <tr>
                    <td>"+ ++index + @"</td>
                    <td colspan=""3"">
                        <a href=""Contents/方法/" + x.Name + @".html"">" + x.Name + @"</a>
                    </td>
                    <td colspan=""2"">" + (x.Comment.IsNotBlank() ? x.Comment : "&nbsp;") + @"</td>
                </tr>");
            });
            functions.IsNotBlank(() =>
            {
                result[2] = @"
        <table bordercolorlight=""#D7D7E5"" bordercolordark=""#D3D8E0"">
            <caption>
                <h2>方法</h2>
            </caption>
            <tr bgcolor=""#F0F0F0"" style='font-weight: bold;'>
                <td>序号</td>
                <td colspan=""3"">方法名称</td>
                <td colspan=""2"">方法说明</td>
            </tr>
" + list.Join("") + "        </table>";
            });

            list.Clear();
            index = 0;
            procedures.IsNotBlank(x =>
            {
                list.Add(@"
                <tr>
                    <td>"+ ++index + @"</td>
                    <td colspan=""3"">
                        <a href=""Contents/存储过程/" + x.Name + @".html"">" + x.Name + @"</a>
                    </td>
                    <td colspan=""2"">" + (x.Comment.IsNotBlank() ? x.Comment : "&nbsp;") + @"</td>
                </tr>");
            });
            procedures.IsNotBlank(() =>
            {
                result[3] = @"
        <table bordercolorlight=""#D7D7E5"" bordercolordark=""#D3D8E0"">
            <caption>
                <h2>存储过程</h2>
            </caption>
            <tr bgcolor=""#F0F0F0"" style='font-weight: bold;'>
                <td>序号</td>
                <td colspan=""3"">存储过程名称</td>
                <td colspan=""2"">存储过程说明</td>
            </tr>
" + list.Join("") + "        </table>";
            });
            return result;
        }

        /*
         * 项目文件
         */
        private string[] GetHHPResultStringArray(string filename, string databaseName, List<string> FilepathList)
        {
            /*
             * ChmFile = $"{database.Name}.chm",
             * DefaultFile = @"Contents\数据库目录.html",
             * Title = $"{database.Name}数据库结构文档",
             * Files = FilepathList
             */
            var result = new string[4];
            result[0] = filename;
            result[1] = @"toc.html";
            result[2] = $"{databaseName}数据库结构文档";
            result[3] = FilepathList.Join(Environment.NewLine);
            return result;
        }

        private string GetColumnsResultString(IList<ColumnModel> columns)
        {
            /*
             * ChmFile = $"{database.Name}.chm",
             * DefaultFile = @"Contents\数据库目录.html",
             * Title = $"{database.Name}数据库结构文档",
             * Files = FilepathList
             */
            var list = new List<string>();
            int index = 0;
            columns.IsNotBlank(x =>
            {
                list.Add(@"
                <tr>
                    <td>" + ++index + @"</td>
                    <td>" + x.Name + @"</td>
                    <td>" + x.Type + @"</td>
                    <td>" + (x.Length.HasValue ? x.Length.Value.ToString() : "&nbsp;") + @"</td>
                    <td>" + (x.Scale.HasValue ? x.Scale.Value.ToString() : "&nbsp;") + @"</td>
                    <td>" + (x.KeyType.IsNotBlank() ? x.KeyType : "&nbsp;") + @"</td>
                    <td>" + (x.IsAutoIncrement ? "√" : "&nbsp;") + @"</td>
                    <td>" + (x.IsNull ? "√" : "&nbsp;") + @"</td>
                    <td>" + (x.Default.IsNotBlank() ? x.Default : "&nbsp;") + @"</td>
                    <td>" + (x.Comment.IsNotBlank() ? x.Comment : "&nbsp;") + @"</td>
                </tr>");
            });
            return list.Join("");
        }

        [DllImport("shell32.dll")]
        public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

        /// <summary>
        /// 搜索获取软件安装目录
        /// </summary>
        /// <param name="orNames">软件名称 或 软件的主程序带exe的文件名</param>
        /// <returns>获取安装目录</returns>
        public static string SearchInstallDir(params string[] orNames)
        {
            //即时刷新注册表
            SHChangeNotify(0x8000000, 0, IntPtr.Zero, IntPtr.Zero);

            string installDir = null;

            var or_install_addrs = new List<string>
            {
                @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
                @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
            };

            var or_get_key_names = new List<string>
            {
                "InstallLocation",
                "InstallPath",
                "Install_Dir",
                "UninstallString"
            };


            Microsoft.Win32.RegistryKey regKey = null;
            try
            {
                regKey = Microsoft.Win32.Registry.LocalMachine;

                var arr_exe = orNames.Where(t => t.EndsWith(".exe")).ToList();
                var arr_name = orNames.Where(t => !t.EndsWith(".exe")).ToList();

                if (arr_exe.Any())
                {
                    foreach (var exe_name in arr_exe)
                    {
                        var name_node = regKey.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{exe_name}", false);
                        if (name_node != null)
                        {
                            var keyValue = name_node.GetValue("Path");

                            if (keyValue == null)
                            {
                                //取 (默认)
                                keyValue = name_node.GetValue("");
                            }

                            if (keyValue != null)
                            {
                                // 值 可能 带双引号，去除双引号
                                installDir = keyValue.ToString().Trim('"');
                                if (!Directory.Exists(installDir))
                                {
                                    // 可能是文件路径，取目录
                                    installDir = Path.GetDirectoryName(installDir);
                                }
                                return installDir;
                            }
                        }
                    }
                }
                else
                {
                    foreach (var regAddr in or_install_addrs)
                    {
                        var regSubKey = regKey.OpenSubKey(regAddr, false);

                        foreach (var name in arr_name)
                        {
                            var name_node = regSubKey.OpenSubKey(name);

                            if (name_node != null)
                            {
                                foreach (var keyName in or_get_key_names)
                                {
                                    var keyValue = name_node.GetValue(keyName);

                                    if (keyValue != null)
                                    {
                                        // 值 可能 带双引号，去除双引号
                                        installDir = keyValue.ToString().Trim('"');
                                        if (!Directory.Exists(installDir))
                                        {
                                            // 可能是文件路径，取目录
                                            installDir = Path.GetDirectoryName(installDir);
                                        }
                                        return installDir;
                                    }
                                }
                            }
                        }

                    }
                }
            }
            catch (Exception ex)
            {
            }
            finally
            {
                regKey?.Close();
            }
            return installDir;
        }
    }
}
